/*
* Copyright (C) 2019 ~ 2022 Uniontech Software Technology Co.,Ltd.
*
* Author:     yanghongwei <yanghongwei@uniontech.com>
*
* Maintainer: yanghongwei <yanghongwei@uniontech.com>
*/

#include "fanotify.h"
#include "abstractnotifyprocess.h"

#include <iostream>
#include <fstream>
#include <thread>
#include <vector>

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fanotify.h>
#include <unistd.h>
#include <string.h>

#include <linux/limits.h>
#include <linux/version.h>

using namespace std;

/**
 * @brief 分割字符串
 * @param s 要分割字符串
 * @param seperator 分割符
 * @return vector<string> 分割后字符串数组
 */
vector<string> split(const string &s, const string &seperator)
{
    vector<string> result;
    std::string::size_type pos1, pos2;
    pos2 = s.find(seperator);
    pos1 = 0;
    while (std::string::npos != pos2) {
        result.push_back(s.substr(pos1, pos2 - pos1));

        pos1 = pos2 + seperator.size();
        pos2 = s.find(seperator, pos1);
    }
    if (pos1 != s.length())
        result.push_back(s.substr(pos1));

    return result;
}

/**
 * @brief 通过pid获取进程名
 * @param pid 进程pid
 * @return string 进程名字,如果未获取到则返回空
 */
string getProcessNameFromPid(int pid)
{
    // TODO: 获取可能存在问题，是否存在不同情况的 cmdline
    char content[PATH_MAX] = {0};
    string file = "/proc/" + to_string(pid) + "/cmdline";

    ifstream ifs;
    ifs.open(file, ios::in);
    ifs.getline(content, sizeof(content) - 1);
    ifs.close();

    const vector<string> &result = split(content, "/");
    if (result.size() <= 1) {
        return content;
    }

    return result.at(result.size() - 1);
}

/**
 * @brief 根据fd获取文件全路径
 * @param fd 文件描述符
 * @return string 文件全路径，如果未找到则返回空
 */
string getFilePathFromFd(int fd)
{
    char path[PATH_MAX] = {0};
    ssize_t path_len;
    string file = "/proc/self/fd/" + to_string(fd);
    path_len = readlink(file.c_str(), path, sizeof(path) - 1);
    if (path_len == -1) {
        return "";
    }
    return path;
}

/**
 * @brief 打印fanotify events
 * @param events
 */
void printFanotifyEvents(uint32_t events)
{
    if (events & FAN_ACCESS) {
        cout << "FAN_ACCESS:";
    }

    if (events & FAN_MODIFY) {
        cout << "FAN_MODIFY:";
    }

    if (events & FAN_CLOSE_WRITE) {
        cout << "FAN_CLOSE_WRITE:";
    }

    if (events & FAN_CLOSE_NOWRITE) {
        cout << "FAN_CLOSE_NOWRITE:";
    }

    if (events & FAN_OPEN) {
        cout << "FAN_OPEN:";
    }

    if (events & FAN_OPEN_PERM) {
        cout << "FAN_OPEN_PERM:";
    }

    if (events & FAN_ACCESS_PERM) {
        cout << "FAN_ACCESS_PERM:";
    }

    if (events & FAN_ONDIR) {
        cout << "FAN_ONDIR:";
    }

// 内核版本要大于5.0
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
    if (events & FAN_OPEN_EXEC) {
        cout << "FAN_OPEN_EXEC:";
    }

    if (events & FAN_ATTRIB) {
        cout << "FAN_ATTRIB:";
    }

    if (events & FAN_CREATE) {
        cout << "FAN_CREATE:";
    }

    if (events & FAN_DELETE) {
        cout << "FAN_DELETE:";
    }

    if (events & FAN_DELETE_SELF) {
        cout << "FAN_DELETE_SELF:";
    }

    if (events & FAN_MOVE_SELF) {
        cout << "FAN_MOVE_SELF:";
    }

    if (events & FAN_MOVED_FROM) {
        cout << "FAN_MOVED_FROM:";
    }

    if (events & FAN_MOVED_TO) {
        cout << "FAN_MOVED_TO:";
    }

    if (events & FAN_OPEN_EXEC_PERM) {
        cout << "FAN_OPEN_EXEC_PERM:";
    }
#endif
    cout << endl;
}

FaNotify::FaNotify()
    : m_running(false)
    , m_fd(-1)
{
}

FaNotify::~FaNotify()
{
    if (m_fd != -1) {
        close(m_fd);
    }
}

/**
 * @brief fanotify的初始化
 * @return true:初始化成功 false:初始化失败
 */
bool FaNotify::init()
{
    if (m_fd != -1) {
        return true;
    }

    m_fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK,
                         O_RDONLY | O_LARGEFILE);
    return m_fd != -1;
}

/**
 * @brief 添加一个要监听的节点
 * @param node 监听节点
 * @return true:添加成功 false:添加失败
 */
bool FaNotify::addNode(const NotifyNode &node)
{
    return addNode(node, true);
}

/**
 * @brief 添加一个要监听的节点
 * @param node 监听节点
 * @param isSave 是否保存节点数据
 * @return true:添加成功 false:添加失败
 */
bool FaNotify::addNode(const NotifyNode &node, bool isSave)
{
    // 未初始化或者文件为空不添加
    if (m_fd == -1 || node.file == "") {
        return false;
    }

    // 根据监听事件类型添加监听
    int err = fanotify_mark(m_fd, FAN_MARK_ADD | FAN_MARK_MOUNT,
                            getFaNotifyEvents(node), AT_FDCWD,
                            node.file.c_str());
    if (err == -1) {
        std::cout << "Cannot watch '" << node.file << "':" << strerror(errno) << std::endl;
        return false;
    }

    if (isSave) {
        // 保存已监控节点
        m_notifyNodeList.push_back(node);
        std::cout << "add file:" << node.file << ",size:" << m_notifyNodeList.size() << std::endl;
    }

    return true;
}

/**
 * @brief 移除一个已经监听的节点
 * @param node 监听节点
 * @return true:移除成功 false:移除失败
 */
bool FaNotify::removeNode(const NotifyNode &node)
{
    // 未初始化或者文件为空不做移除操作
    if (m_fd == -1 || node.file == "") {
        return false;
    }

    // 进行移除操作
    int err = fanotify_mark(m_fd, FAN_MARK_REMOVE | FAN_MARK_MOUNT, getFaNotifyEvents(node), AT_FDCWD,
                            node.file.c_str());
    if (err == -1) {
        std::cout << "Cannot remove watch '" << node.file << "':" << strerror(errno) << std::endl;
        return false;
    }

    // 移除FAN_MARK_MOUNT，会导致其它同一挂载点的监控失效，所以在移除一个之后将其它原有监控重新监控一次
    for (auto item = m_notifyNodeList.begin(); item != m_notifyNodeList.end();) {
        if (*item == node) {
            item = m_notifyNodeList.erase(item);
            continue;
        }

        addNode(*item, false);
        item++;
    }

    return true;
}

/**
 * @brief 启动监听线程
 * @return true 启动成功，false 启动失败
 */
bool FaNotify::start()
{
    // 未初始化启动失败
    if (m_fd == -1) {
        return false;
    }

    // 已经启动
    if (m_running.load()) {
        return true;
    }

    m_running.store(true);
    return AbstractNotify::start();
}

/**
 * @brief 停止监听线程
 * @return true 停止成功，false 停止失败
 */
bool FaNotify::stop()
{
    if (m_running.load()) {
        m_running.store(false);
    }

    return true;
}

/**
 * @brief 线程执行体
 */
void FaNotify::run()
{
    int result = -1; // poll返回值
    nfds_t nfds;
    struct pollfd fds[1];
    /* Prepare for polling. */
    nfds = 1;

    fds[0].fd = m_fd; /* Fanotify input */
    fds[0].events = POLLIN; // m_fd有可读数据时触发poll

    /* Wait for events and/or terminal input. */
    std::cout << "Listening for fanotify events." << std::endl;
    while (m_running.load()) {
        // 5S超时
        result = poll(fds, nfds, 5000);
        if (result == -1) {
            if (errno == EINTR)
                continue;
            perror("poll");
            m_running.store(false);
            break;
        }

        // 超时继续
        if (result == 0) {
            continue;
        }

        // 判断出发事件是否为POLLIN,如果不是则退出等待下一个事件
        if (!(fds[0].revents & POLLIN)) {
            continue;
        }

        struct fanotify_event_metadata buf[200];
        ssize_t len;

        /* Loop while events can be read from fanotify file descriptor. */
        for (;;) {
            /* Read some events. */
            len = read(m_fd, buf, sizeof(buf));
            if (len == -1 && errno != EAGAIN) {
                perror("read");
                m_running.store(false);
                break;
            }

            /* Check if end of available data reached. */
            if (len <= 0)
                break;

            /* Loop over all events in the buffer. */
            handleEvent(buf, len);
        }
    }

    std::cout << "Listening for events stopped." << std::endl;

    /* Close inotify file descriptor. */
    close(m_fd);
    m_fd = -1;
}

/**
 * @brief 线程运行状态
 * @return true:线程在运行 false:线程停止
 */
bool FaNotify::isRunning()
{
    return m_running.load();
}

/**
 * @brief FileEvent转换为fanotify event
 * @param node 监听节点
 * @return fanotify events
 */
uint32_t FaNotify::getFaNotifyEvents(const NotifyNode &node)
{
    uint32_t events = 0;

    if (node.events & ACCESS) {
        events |= FAN_ACCESS;
    }

    if (node.events & MODIFY) {
        events |= FAN_MODIFY;
    }

    if (node.events & CLOSE_WRITE) {
        events |= FAN_CLOSE_WRITE;
    }

    if (node.events & CLOSE_NOWRITE) {
        events |= FAN_CLOSE_NOWRITE;
    }

    if (node.events & OPEN) {
        events |= FAN_OPEN;
    }

    if (node.events & OPEN_PERM) {
        events |= FAN_OPEN_PERM;
    }

    if (node.events & ACCESS_PERM) {
        events |= FAN_ACCESS_PERM;
    }

    if (node.events & ONDIR) {
        events |= FAN_ONDIR;
    }

// 内核版本要大于5.0
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
    if (node.events & OPEN_EXEC) {
        events |= FAN_OPEN_EXEC;
    }

    if (node.events & ATTRIB) {
        events |= FAN_ATTRIB;
    }

    if (node.events & CREATE) {
        events |= FAN_CREATE;
    }

    if (node.events & DELETE) {
        events |= FAN_DELETE;
    }

    if (node.events & DELETE_SELF) {
        events |= FAN_DELETE_SELF;
    }

    if (node.events & MOVE_SELF) {
        events |= FAN_MOVE_SELF;
    }

    if (node.events & MOVED_FROM) {
        events |= FAN_MOVED_FROM;
    }

    if (node.events & MOVED_TO) {
        events |= FAN_MOVED_TO;
    }

    if (node.events & OPEN_EXEC_PERM) {
        events |= FAN_OPEN_EXEC_PERM;
    }
#endif

    return events;
}

/**
 * @brief fanotify event转换为FileEvent
 * @param events fanotify events
 * @return FileEvents
 */
uint32_t FaNotify::getFileNotifyEvents(const uint32_t &events)
{
    uint32_t tmpEvents = 0;

    if (events & FAN_ACCESS) {
        tmpEvents |= ACCESS;
    }

    if (events & FAN_MODIFY) {
        tmpEvents |= MODIFY;
    }

    if (events & FAN_CLOSE_WRITE) {
        tmpEvents |= CLOSE_WRITE;
    }

    if (events & FAN_CLOSE_NOWRITE) {
        tmpEvents |= CLOSE_NOWRITE;
    }

    if (events & FAN_OPEN) {
        tmpEvents |= OPEN;
    }

    if (events & FAN_OPEN_PERM) {
        tmpEvents |= OPEN_PERM;
    }

    if (events & FAN_ACCESS_PERM) {
        tmpEvents |= ACCESS_PERM;
    }

    if (events & FAN_ONDIR) {
        tmpEvents |= ONDIR;
    }

// 内核版本要大于5.0
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
    if (events & FAN_OPEN_EXEC) {
        tmpEvents |= OPEN_EXEC;
    }

    if (events & FAN_ATTRIB) {
        tmpEvents |= ATTRIB;
    }

    if (events & FAN_CREATE) {
        tmpEvents |= CREATE;
    }

    if (events & FAN_DELETE) {
        tmpEvents |= DELETE;
    }

    if (events & FAN_DELETE_SELF) {
        tmpEvents |= DELETE_SELF;
    }

    if (events & FAN_MOVE_SELF) {
        tmpEvents |= MOVE_SELF;
    }

    if (events & FAN_MOVED_FROM) {
        tmpEvents |= MOVED_FROM;
    }

    if (events & FAN_MOVED_TO) {
        tmpEvents |= MOVED_TO;
    }

    if (events & FAN_OPEN_EXEC_PERM) {
        tmpEvents |= OPEN_EXEC_PERM;
    }
#endif

    return tmpEvents;
}

/**
 * @brief fanotify事件信号处理
 * @param metadata 事件数据指针
 * @param len 数据长度
 */
void FaNotify::handleEvent(struct fanotify_event_metadata *metadata, int len)
{
    struct fanotify_response response;

    /* Loop over all events in the buffer. */
    while (FAN_EVENT_OK(metadata, len)) {
        /* Check that run-time and compile-time structures match. */
        if (metadata->vers != FANOTIFY_METADATA_VERSION) {
            fprintf(stderr,
                    "Mismatch of fanotify metadata version.\n");
            m_running.store(false);
            break;
        }

        /* metadata->fd contains either FAN_NOFD, indicating a
                      queue overflow, or a file descriptor (a nonnegative
                      integer). Here, we simply ignore queue overflow. */
        if (metadata->fd >= 0) {
            printFanotifyEvents(metadata->mask);
            // 设置权限回应数据
            response.fd = metadata->fd;
            response.response = FAN_ALLOW;

            // 获取文件全路径以及进程名称
            string filePath = getFilePathFromFd(metadata->fd);
            string process = getProcessNameFromPid(metadata->pid);

            // 获取当前时间
            time_t now = time(NULL);
            cout << "now:" << now << " filePath:" << filePath << " process:" << process << endl;
            // 将fanotify events转为filenotify events
            uint32_t fileEvents = getFileNotifyEvents(metadata->mask);
            // 回调通知事件
            for (auto &callbackFun : mapCallbackFuns) {
                // 回调返回false，则阻断操作
                if (!callbackFun.second(now, filePath, process, fileEvents)) {
                    response.response = FAN_DENY;
                }
            }

            write(m_fd, &response, sizeof(response));

            /* Close the file descriptor of the event. */
            close(metadata->fd);
        }

        /* Advance to next event. */
        metadata = FAN_EVENT_NEXT(metadata, len);
    }
}
